import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
 * Class that handles the command that was written in an appropriate way.
 * Implements all the abstract methods declared in {@link LolBotEventListener}.
 * There is one method for each command.
 * 
 * @author Tobias Johansson
 * 
 */
public class LolBotCommandHandler implements LolBotEventListener {
	private BufferedReader in;
	private BufferedWriter out;
	private String channel;

	private List<String> opList;
	protected static List<String> badWordsList;
	private List<String> ignoreList;

	private static final String OWNER = "tobijoh";
	private static final String OP_LIST = "assets/OPlist.txt";
	private static final String BAD_WORDS_LIST = "assets/BadWords.txt";
	private static final String IGNORE_LIST = "assets/Ignore.txt";
	private static final String BASIC_COMMANDS = "help | date | listOfOps | listOfBadWords | nithander | "
			+ "phoneHome";
	private static final String OP_COMMANDS = "help | date | op | listOfOps | listOfBadWords | addOp | "
			+ "delOp | addBadWord | delBadWord| nithander | phoneHome |"
			+ " russian_roulette | sing | stopSinging | blopp";

	private boolean iShouldSing = false;

	// Hannes was here.
	// True when a manly fight of russian roulette is going on.
	private boolean isFight = false;
	// Indicates which chamber of the russian roulette revolver has a bullet.
	private int revolverCounter = 0;

	/**
	 * Create a new {@link LolBotCommandHandler}.
	 * 
	 * @param in
	 *            The input stream from the socket. We read what is written in
	 *            the chat from here.
	 * @param out
	 *            The output stream from the socket. We write all our text to
	 *            this stream.
	 * @param channel
	 *            The IRC channel that LolBot is connected to.
	 */
	public LolBotCommandHandler(BufferedReader in, BufferedWriter out,
			String channel) {
		this.in = in;
		this.out = out;

		opList = new ArrayList<String>();
		badWordsList = new ArrayList<String>();
		ignoreList = new ArrayList<String>();

		readFileToList(OP_LIST, opList);
		readFileToList(BAD_WORDS_LIST, badWordsList);
		readFileToList(IGNORE_LIST, ignoreList);
	}

	// ///////////////////////////////////////////////
	// Commands that LolBot listens to.
	// ///////////////////////////////////////////////
	@Override
	public void onPing() {
		try {
			out.write("PONG " + channel + "\n");
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		checkOps();
	}

	@Override
	public void onJoin(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (isOnOpList(sourceNick) && !isIgnored(sourceNick)) {
			op(channel, sourceNick);
		}

		if (!isIgnored(sourceNick)
				&& (sourceNick.toLowerCase().equals("majstroll")
						|| sourceNick.toLowerCase().equals("nithande") || sourceNick
						.toLowerCase().equals("nithander"))) {
			drawTrollface();
		}
	}

	@Override
	public void onExit(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (sourceNick.equals(OWNER)) {
			writeListToFile(opList, OP_LIST);
			writeListToFile(badWordsList, BAD_WORDS_LIST);
			writeListToFile(ignoreList, IGNORE_LIST);

			try {
				out.write("QUIT " + channel + "\r\n");
				out.flush();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		} else if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick + ": "
					+ "You are not my master, STFU :@");
		}
	}

	@Override
	public void onLolBotKicked(LolBotEvent e) {
		writeListToFile(opList, OP_LIST);
		writeListToFile(badWordsList, BAD_WORDS_LIST);
		writeListToFile(ignoreList, IGNORE_LIST);

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		try {
			out.write("JOIN " + channel + "\r\n");
			out.flush();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}

	@Override
	public void onOp(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick) && isOnOpList(e.getSourceNick())) {
			op(channel, e.getArguments());
		}
	}

	@Override
	public void onSay(LolBotEvent e) {
		if (e.getSourceNick().equals(OWNER)) {
			String message = "";
			for (String word : e.getArguments()) {
				message += word + " ";
			}
			sendChannelMessage(channel, message);
		}
	}

	@Override
	public void onMe(LolBotEvent e) {
		if (e.getSourceNick().equals(OWNER)) {
			String message = "";
			for (String word : e.getArguments())
				message += " " + word;
			sendChannelMessage(channel, "\001ACTION" + message + "\001");
		}
	}

	@Override
	public void onAddOp(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String opNick : arguments) {
				if (!isOnOpList(opNick)) {
					opList.add(opNick);
					sendChannelMessage(channel, "Added " + opNick
							+ " to the OP list!");
				} else {
					sendChannelMessage(channel, opNick
							+ " is already on the OP list!");
				}
			}
			Collections.sort(opList);
			writeListToFile(opList, OP_LIST);
		}
	}

	@Override
	public void onDelOp(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String delNick : arguments) {
				if (isOnOpList(delNick)) {
					opList.remove(delNick);
					sendChannelMessage(channel, "Deleted " + delNick
							+ " from the OP list!");
				} else {
					sendChannelMessage(channel, delNick
							+ " is not on the OP list!");
				}
			}
			Collections.sort(opList);
			writeListToFile(opList, OP_LIST);
		}
	}

	@Override
	public void onAddBadWord(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String badWord : arguments) {
				if (!badWordsList.contains(badWord)) {
					badWordsList.add(badWord);
					sendChannelMessage(channel, "Added " + badWord
							+ " to the Bad words list!");
				} else {
					sendChannelMessage(channel, badWord
							+ " is already on the Bad words list!");
				}
			}
			Collections.sort(badWordsList);
			writeListToFile(badWordsList, BAD_WORDS_LIST);
		}
	}

	@Override
	public void onDelBadWord(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String badWord : arguments) {
				if (badWordsList.contains(badWord)) {
					badWordsList.remove(badWord);
					sendChannelMessage(channel, "Deleted " + badWord
							+ " from the Bad words list!");
				} else {
					sendChannelMessage(channel, badWord
							+ " is not on the Bad words list!");
				}
			}
			Collections.sort(badWordsList);
			writeListToFile(badWordsList, BAD_WORDS_LIST);
		}
	}

	@Override
	public void onIgnore(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String ignoreNick : arguments) {
				if (!isIgnored(ignoreNick)) {
					ignoreList.add(ignoreNick);
					sendChannelMessage(channel, "Added " + ignoreNick
							+ " to the ignore list!");
				} else {
					sendChannelMessage(channel, ignoreNick
							+ " is already on the ignore list!");
				}
			}
			Collections.sort(ignoreList);
			writeListToFile(ignoreList, IGNORE_LIST);
		}
	}

	@Override
	public void onUnignore(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> arguments = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			for (String ignoreNick : arguments) {
				if (isIgnored(ignoreNick)) {
					ignoreList.remove(ignoreNick);
					sendChannelMessage(channel, "Deleted " + ignoreNick
							+ " from the ignore list!");
				} else {
					sendChannelMessage(channel, ignoreNick
							+ " is not on the ignore list!");
				}
			}
			Collections.sort(ignoreList);
			writeListToFile(ignoreList, IGNORE_LIST);
		}
	}

	@Override
	public void onHelp(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			if (!isOnOpList(sourceNick)) {
				sendChannelMessage(channel, sourceNick + ": "
						+ "You are not on the OP list, so these commands"
						+ " are available for you:");
				sendChannelMessage(channel, BASIC_COMMANDS);
			} else {
				sendChannelMessage(channel, sourceNick + ": "
						+ "You are on the OP list, so these commands"
						+ " are available for you:");
				sendChannelMessage(channel, OP_COMMANDS);
			}
		}
	}

	@Override
	public void onNithander(LolBotEvent e) {
		if (!isIgnored(e.getSourceNick())) {
			drawTrollface();
		}
	}

	@Override
	public void onSing(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		List<String> song = e.getArguments();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			if (song.isEmpty()) {
				sendChannelMessage(channel, sourceNick + ": These are "
						+ "the songs I know:");
				sendChannelMessage(channel, getSongs());
				sendChannelMessage(channel, "Or if you want to add " +
						"another song to my library, please go to " +
						"http://tobijoh.se/LolBot/upload_song/");
			} else if (song.size() == 1) {
				iShouldSing = true;
				final String songToPlay = e.getArguments().get(0);
				singSong(songToPlay);
			} else {
				String mergedSong = song.get(0);
				for (int i = 1; i < song.size(); i++) {
					mergedSong += " " + song.get(i);
				}
				iShouldSing = true;
				final String songToPlay = mergedSong;
				singSong(songToPlay);
			}
		}
	}

	@Override
	public void onStopSinging(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			iShouldSing = false;
		}
	}

	@Override
	public void onDate(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick + ": " + new Date());
		}
	}

	@Override
	public void onPhoneHome(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick
					+ ": http://code.google.com/p/lolbot2/");
		}
	}

	@Override
	public void onBlopp(LolBotEvent e) {
		if (!isIgnored(e.getSourceNick())) {
			sendChannelMessage(channel, ".blipp");
		}
	}

	@Override
	public void onRussianRoulette(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		if (!isIgnored(sourceNick) && isOnOpList(sourceNick)) {
			isFight = true;
			Random rG = new Random();
			revolverCounter = rG.nextInt(6);
			sendChannelMessage(channel, "Gun loaded. Let's play!");
		}
	}

	@Override
	public void onClick(LolBotEvent e) {
		String sourceNick = e.getSourceNick();
		if (!isIgnored(sourceNick) && isOnOpList(sourceNick) && isFight) {
			// This would mean that the bullet was in the first chamber
			if (revolverCounter == 0) {
				try {
					out.write("KICK " + channel + " " + sourceNick + "\r\n");
					out.flush();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				sendChannelMessage(channel, "A loud *BANG* as the gun "
						+ "goes off, then a *THUD* as " + sourceNick
						+ "'s head hits the table.");
				isFight = false;
			} else {
				sendChannelMessage(channel, "*CLICK* Lucky!");
				revolverCounter--;
			}
		}
	}

	@Override
	public void onSpin(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick) && isOnOpList(sourceNick) && isFight) {
			sendChannelMessage(channel, "Now where will the bullet go?");
			Random rG = new Random();
			revolverCounter = rG.nextInt(6);
		}
	}

	@Override
	public void onListOfOps(LolBotEvent e) {
		if (!isIgnored(e.getSourceNick())) {
			String listOfOps = opList.get(0);
			for (int i = 1; i < opList.size(); i++) {
				listOfOps += ", " + opList.get(i);
			}
			sendChannelMessage(channel, listOfOps);
		}
	}

	@Override
	public void onListOfBadWords(LolBotEvent e) {
		if (!isIgnored(e.getSourceNick())) {
			String wordList = "\003" + badWordsList.get(0);
			for (String word : badWordsList) {
				if ((wordList.indexOf(word)) <= -1) {
					wordList += ", " + word;
				}
			}
			sendChannelMessage(channel, wordList);
		}
	}

	@Override
	public void onRendezvous(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick
					+ ": Menar du inte \"Déja vu\"?");
		}
	}

	@Override
	public void onMesserschmidt(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick
					+ ": Besserwisser heter det faktiskt!");
		}
	}

	@Override
	public void onBadWord(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick + ": " + e.getBadWord()
					+ " is a bad word! " + "No Christmas gifts this year!");
		}
	}

	@Override
	public void onSlap(LolBotEvent e) {
		String sourceNick = e.getSourceNick();

		if (!isIgnored(sourceNick)) {
			sendChannelMessage(channel, sourceNick
					+ ": Please don't hurt me :-(");
		}
	}

	// ///////////////////////////////////////////////
	// ///////////////////////////////////////////////

	// ///////////////////////////////////////////////
	// Helper methods.
	// ///////////////////////////////////////////////
	/**
	 * Draw Trollface with unicode characters in the channel.
	 */
	private void drawTrollface() {
		BufferedReader trollBuf;
		try {
			trollBuf = new BufferedReader(
					new FileReader("assets/trollface.txt"));
			String trollLine = "";
			String[] trollface = new String[16];
			int lineCount = 0;
			while ((trollLine = trollBuf.readLine()) != null) {
				trollface[lineCount] = trollLine;
				lineCount++;
			}
			for (String t : trollface) {
				sendChannelMessage(channel, t);
			}
		} catch (FileNotFoundException e) {
			System.out.println("I can't draw Trollface, "
					+ "because I don't know what he looks like :'(");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Makes LolBot sing a song!
	 * 
	 * @param fileName
	 *            The name of the file containing the text.
	 */
	private void singSong(final String fileName) {
		new Thread() {
			public void run() {
//				BufferedReader song;
				try {
					System.out.println(fileName);
					URL songURL = 
							new URL("http://tobijoh.se/" +
									"LolBot/upload_song/songs/" 
									+ fileName + ".txt");
					InputStream is = songURL.openStream();
					BufferedReader song = new BufferedReader(
							new InputStreamReader(is));
					
//					song = new BufferedReader(new FileReader(fileName));
					String line = "";
					while ((line = song.readLine()) != null && iShouldSing) {
						sendChannelMessage(channel, "♪ " + line);
						Thread.sleep(1000);
					}
				} catch (FileNotFoundException e) {
					sendChannelMessage(channel,
							"Hmm, I don't know that song :S " +
							"Try one of these instead:");
					sendChannelMessage(channel, getSongs());
					sendChannelMessage(channel, "Or if you want to add " +
							"another song to my library, please go to " +
							"http://tobijoh.se/LolBot/upload_song/");

				} catch (IOException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}

	/**
	 * Creates a list with all the songs that LolBot can sing.
	 * 
	 * @return A list with all the songs that LolBot can sing.
	 */
	private String getSongs() {
		String songList = "";

		try {
			URL songURL = 
					new URL("http://tobijoh.se/LolBot/upload_song/songs/");
			InputStream is = songURL.openStream();
			BufferedReader bIn = new BufferedReader(
					new InputStreamReader(is));

			String songListFromUrl = bIn.readLine();
			String[] songs = songListFromUrl.split(" ");

			StringBuffer songListBuffer = new StringBuffer();
			if (songs.length > 0) {
				songListBuffer.append(songs[0].substring(0, 
						songs[0].indexOf('.')));
				for (int i = 1; i < songs.length; i++) {
					songListBuffer.append(", ");
					songListBuffer.append(songs[i].substring(0, 
							songs[i].indexOf('.')));
				}
			}
			
			songList = songListBuffer.toString();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return songList;
	}

	/**
	 * Method that checks if all the online users that are on the OP list have
	 * OP. If not, they will get OP. This method is called at every PING.
	 * 
	 */
	private void checkOps() {

		System.out.println("Checking OPs...");
		try {
			out.write("NAMES #luder" + "\r\n");
			out.flush();
			String[] names = in.readLine()
					.substring(in.readLine().indexOf("[") + 1).split(" ");
			for (String name : names) {
				if (!name.startsWith("@") && isOnOpList(name)) {
					out.write("MODE " + channel + " +o " + name + "\r\n");
					out.flush();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Method that OPs several people.
	 * 
	 * @param channel
	 *            The channel that LolBot is in.
	 * @param list
	 *            Nicknames of the persons to get OP.
	 */
	private void op(String channel, List<String> list) {
		for (String nick : list) {
			op(channel, nick);
		}
	}

	/**
	 * Method that OPs a person.
	 * 
	 * @param channel
	 *            The channel that LolBot is in.
	 * @param nick
	 *            Nickname of the person to get OP.
	 */
	private void op(String channel, String nick) {
		try {
			out.write("MODE " + channel + " +o " + nick + "\r\n");
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Checks if this nick is on the ignore list.
	 * 
	 * @param nick
	 *            The nick to check.
	 * @return {@code true} if ignored, {@code false} otherwise.
	 */
	private boolean isIgnored(String nick) {
		return ignoreList.contains(nick);
	}

	/**
	 * Checks if this nick is on the OP list.
	 * 
	 * @param nick
	 *            The nick to check.
	 * @return {@code true} if on OP list, {@code false} otherwise.
	 */
	private boolean isOnOpList(String nick) {
		return opList.contains(nick);
	}

	/**
	 * Method that sends a message to a channel
	 * 
	 * @param channel
	 *            The channel to which the message is going to be sent.
	 * @param message
	 *            The message.
	 */
	private void sendChannelMessage(String channel, String message) {
		try {
			out.write("PRIVMSG " + channel + " :" + message + "\r\n");
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Method that sends a private message to a nick
	 * 
	 * @param nick
	 *            The nick to which the message is going to be sent.
	 * @param message
	 *            The message.
	 */
	private void sendPrivateMessage(String nick, String message) {
		try {
			out.write("PRIVMSG " + nick + " :" + message + "\r\n");
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Reads a text file containing one word per line to a list.
	 * 
	 * @param fileName
	 *            The text file to read from.
	 * @param list
	 *            The list to write to.
	 */
	private void readFileToList(String fileName, List<String> list) {
		try {
			BufferedReader br = new BufferedReader(new FileReader(fileName));

			String word;
			// Read in the words from the text file.
			while ((word = br.readLine()) != null) {
				if (!list.contains(word)) {
					list.add(word);
				}
			}
			br.close();

			Collections.sort(list);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Writes a list to a text file.
	 * 
	 * @param list
	 *            The list that is to be written to a text file.
	 * @param fileName
	 *            The text file to write to.
	 */
	private void writeListToFile(List<String> list, String fileName) {
		try {
			FileWriter fw = new FileWriter(fileName);
			for (String word : list) {
				fw.write(word + "\r\n");
			}
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void setBuffers(BufferedReader in, BufferedWriter out) {
		this.in = in;
		this.out = out;
	}

	public void setChannel(String channel) {
		this.channel = channel;
	}
	// ///////////////////////////////////////////////
	// ///////////////////////////////////////////////
}
